Skip to content

[CXF-8926] replace PipedInputStream with TimedBlockingPipe to fix MTO…#3250

Open
ffang wants to merge 1 commit into
apache:mainfrom
ffang:CXF-8926
Open

[CXF-8926] replace PipedInputStream with TimedBlockingPipe to fix MTO…#3250
ffang wants to merge 1 commit into
apache:mainfrom
ffang:CXF-8926

Conversation

@ffang

@ffang ffang commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

…M attachment stall

Summary

Fixes CXF-8926: a CXF client sending a large request body (e.g. a MTOM attachment) to a slow or stalled server hangs indefinitely with no way to recover.

Root cause

Two conduits are affected by the same underlying problem — the CXF writer thread can be permanently blocked on a request-body write with no effect from receiveTimeout:

HttpClientHTTPConduit — CXF serializes the request body into a PipedOutputStream; the JDK HttpClient drains the connected PipedInputStream on a cached thread-pool thread. When the server reads slowly (or not at all), OS TCP send/receive buffers fill, the HttpClient thread stalls inside the pipe, and PipedOutputStream.write() blocks in awaitSpace() forever. Compounding this, after ~60 s of inactivity the JDK cached-pool idle-reaps the draining thread; the next write then throws java.io.IOException: Read end deadCould not write attachments even though the server never closed the connection.

URLConnectionHTTPConduit — writes directly to HttpURLConnection$StreamingOutputStream; when OS TCP buffers fill the socket write blocks indefinitely. No pipe or thread-reap is involved — it simply hangs.

In both cases receiveTimeout has no effect because it only governs the HTTP response read timeout, not the request-body write path.

Fix

HttpClientHTTPConduit: replace PipedInputStream / PipedOutputStream with a new TimedBlockingPipe — a purpose-built, lock-based circular byte buffer. Key differences from the JDK pipe:

  • No thread-identity check: the JDK idle reap is invisible to it — the write just blocks until the reader resumes or the deadline expires.
  • Write timeout: when receiveTimeout > 0, writes fail with a bounded IOException instead of hanging forever; when receiveTimeout = 0, writes block indefinitely (same as before, but without the Read end dead risk).

URLConnectionHTTPConduit: interpose the same TimedBlockingPipe between the CXF writer and the socket output. A background daemon thread drains the pipe and pushes bytes to the socket; close() on the pipe side waits for the copier to finish before handleResponse() reads the reply.

Tests added (MTOMAttachmentStallTest)

Test Scenario Conduits exercised
testMtomClientDoesNotHangWhenNetworkStalls Raw ServerSocket accepts but never reads; OS TCP buffers saturate Both (default + forceURLConnection)
testMtomClientSucceedsWhenServerResumesAfterStall Real CXF/Jetty server stalls ~75 s mid-stream then resumes; client must complete successfully HttpClientHTTPConduit

testMtomClientDoesNotHangWhenNetworkStalls sets receiveTimeout = 5 s and asserts the client unblocks within 20 s.
testMtomClientSucceedsWhenServerResumesAfterStall sets receiveTimeout = 0 (infinite) and asserts the call succeeds once back-pressure releases — proving TimedBlockingPipe survives the stall that previously caused Read end dead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant